package jack.handler;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * created by Jack
 * email:yucrun@163.com
 * date: 2019/5/19
 * describe:消息队列[难点和重点]
 *
 * 3.1.定义消息入队和消息出队的方法；
 * 3.2.回到handler中，查看1.10；
 *
 * 3.3.消息的入列，和消息的出列，生产者和消费者模式；
 * 3.4.选择一个折中的方式实现，消息入列和出列；
 *
 * 3.5.消息的维护、存储需要有一个数据结构，这里使用数组来存储；
 * 3.6.定义Message[]成员变量；
 * 3.7.构造方法初始化items，给定数组的大小为50(自己定义)；
 * 3.8.入队和出队的索引位置putIndex,takeIndex;
 * 3.9.在enqueueMessage中执行消息入列的逻辑；
 * 3.10.在Next中执行消息出列的逻辑；
 *
 * 3.11.若生产者队列已满，需要停止生产，等待消费者消费；
 * 3.12.若消费者队列将消息全部消费，等待生产者线程生产；
 * 3.13.需要用到锁机制；
 * 3.15.定义锁和相关条件变量；
 * 3.16.在MessageQueue构造中初始化3.15中定义的变量；
 *
 * 3.17.在enqueueMessage中指定判断逻辑；
 * 3.18.生产者，队列已满，停止生产，等待消费线程消费;
 * 3.19.消费了一个产品，此时队列不是满的了，通知生产者线程生产;
 * 3.20.同样的，消费者消费了全部的消息，一样需要通知生产者生产消息，在Next方法中执行判断逻辑；
 *
 * 3.21.注意问题：考虑到生产者消费者的线程可能存在多个的情况下，调用signal只能唤醒单个线程。需要将signal更改为signalAll;
 *
 */
public class MessageQueue {

    Message[] items;

    //入队和出队的索引位置
    int putIndex;
    int takeIndex;

    int count;

    Lock mLock;
    //条件变量
    Condition notEmpty;             //效率高于同步代码块
    Condition notFull;

    public MessageQueue() {

        //Exception in thread "main" java.lang.RuntimeException: Stub!
        //Log.i("Handler ", "12346" + "MessageQueue");
        //分析了下原因是：importandroid.util.Log;这里用的是Android的库
        //在java中是不能这么用的。

        this.items = new Message[50];
        this.mLock = new ReentrantLock();       //可重入的锁
        this.notEmpty = mLock.newCondition();   //通过mLock得到条件变量
        this.notFull = mLock.newCondition();
    }

    /**
     * 消息入列     生产者
     *
     * @param msg
     */
    public void enqueueMessage(Message msg) {
        //生产者，队列已满，停止生产，等待消费线程消费
        try{
            mLock.lock();
            while (count == items.length){
                //阻塞
                notFull.await();
                //会一直阻塞吗？？？
                //消费者消费了一个，就需要收到通知取消阻塞，在消费中执行对应的逻辑
            }

            //实现了消息入队
            items[putIndex] = msg;

            //改变putIndex的值,考虑到消息超过队列的最大容量，防止putIndex越界的问题
            putIndex = (++putIndex == items.length) ? 0 : putIndex;

            //队列不在为空，有产品可以消费了，通知消费者线程
//            notEmpty.signal();
            notEmpty.signalAll();

            //每有一个消息入列，count+1
            count++;

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();             //最终都要执行的
        }
    }

    /**
     * 消息出队      消费者
     *
     * @return
     */
    public Message Next() {
        Message msg = null;

        try {

            mLock.lock();       //需要对mLock进行了解

            while (count == 0){
                //没有消息，阻塞消费线程
                notEmpty.await();
                //在enqueueMessage方法中，若又生成了一条消息，则通知消费者取消阻塞
            }

            msg = items[takeIndex];

            //元素被取出去之后，items[takeIndex]置空
            items[takeIndex] = null;

            takeIndex = (++takeIndex == items.length) ? 0 : takeIndex;

            //每有一个消息出列，count-1
            count--;

            //消费了一个产品，此时队列不是满的了，通知生产者线程生产
//            notFull.signal();
            notFull.signalAll();

        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            mLock.unlock();         //释放锁
        }

        return msg;
    }

}
